/*
 * Copyright 2007 Agile Plugins Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.agileplugins.checkstyle;

import com.intellij.codeHighlighting.HighlightDisplayLevel;
import com.intellij.codeInspection.InspectionManager;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.puppycrawl.tools.checkstyle.Checker;
import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
import com.puppycrawl.tools.checkstyle.PropertyResolver;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.api.Configuration;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.io.File;
import java.util.HashMap;
import java.util.Map;

public class CheckstyleInspection extends LocalInspectionTool {
    private CheckStyleConfiguration globalConfig = new CheckStyleConfiguration();

    private Logger logger = Logger.getInstance(getClass().getName());

    private Map checkerCache = new HashMap();
    private Map checkerLastModified = new HashMap();
    private CheckStyleOptionsPanel optionsPanel;

    public boolean isEnabledByDefault() {
        return true;
    }

    public HighlightDisplayLevel getDefaultLevel() {
        return HighlightDisplayLevel.WARNING;
    }

    public JComponent createOptionsPanel() {
        optionsPanel = new CheckStyleOptionsPanel(globalConfig);
        return optionsPanel;
    }


    private Checker setUpChecker(PsiClass aClass) {
        final PsiManager manager = aClass.getManager();
        final PsiFile file = aClass.getContainingFile();
        final VirtualFile virtualFile = file.getVirtualFile();
        final Project project = manager.getProject();
        final ProjectRootManager rootManager = ProjectRootManager.getInstance(project);
        final ProjectFileIndex fileIndex = rootManager.getFileIndex();
        Module moduleForFile = fileIndex.getModuleForFile(virtualFile);
        String modulePath = moduleForFile.getModuleFile().getParent().getPath();
        String projectPath = project.getBaseDir().getPath();
        boolean testSource = fileIndex.isInTestSourceContent(virtualFile);

        CheckStyleConfiguration config = CheckStyleConfiguration.getInstance(moduleForFile);
        String configFile = config.getConfigFile(testSource);
        if (configFile == null || configFile.length() == 0) {
            configFile = globalConfig.getConfigFile(testSource);
        }
        configFile = configFile.replaceFirst("\\$\\{MODULE\\}", modulePath);
        configFile = configFile.replaceFirst("\\$\\{PROJECT\\}", projectPath);
        logger.info("configFile = " + configFile);
        return getChecker(configFile);
    }

    private Checker getChecker(final String checkStyleConfigFile) {
        File file = new File(checkStyleConfigFile);
        if (!file.exists()) {
            logger.info("file does not exist! " + file);
            return null;
        }
        Checker checker = (Checker) checkerCache.get(checkStyleConfigFile);

        Long lastModified = (Long) checkerLastModified.get(checkStyleConfigFile);
        if (lastModified != null && file.lastModified() != lastModified.longValue()) {
            checker = null;
        }
        if (checker != null) {
            return checker;
        }
        try {
            Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
            checker = new Checker();
            checker.setClassloader(getClass().getClassLoader());
            PropertyResolver propertyResolver = new CheckStylePropertyResolver();
            Configuration configuration = ConfigurationLoader.loadConfiguration(
                    checkStyleConfigFile, propertyResolver);
            checker.configure(configuration);
            checkerCache.put(checkStyleConfigFile, checker);
            checkerLastModified.put(checkStyleConfigFile, file.lastModified());
            return checker;
        } catch (final CheckstyleException e) {
            logger.error(e);
            return null;
        }
    }

    public ProblemDescriptor[] checkClass(@NotNull final PsiClass psiClass,
                                          @NotNull final InspectionManager inspectionManager,
                                          final boolean b) {

        Checker checker = setUpChecker(psiClass);
        if (checker == null) {
            return new ProblemDescriptor[0];
        }
        VirtualFile virtualFile = psiClass.getContainingFile().getVirtualFile();
        String path = virtualFile.getPath();
        PsiDocumentManager documentManager =
                PsiDocumentManager.getInstance(psiClass.getProject());
        final Document document =
                documentManager.getDocument(psiClass.getContainingFile());
        boolean documentUnsaved =
                FileDocumentManager.getInstance().isDocumentUnsaved(document);
        if (documentUnsaved) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    FileDocumentManager.getInstance().saveDocument(document);
                }
            });
            return null;
        } else {
            CheckStyleAuditListener auditListener = new CheckStyleAuditListener(inspectionManager, psiClass);
            checker.addListener(auditListener);
            checker.process(new File[]{new File(path)});
            checker.removeListener(auditListener);
            return auditListener.getProblemDescriptors();
        }
    }


    @NotNull
    public final String getGroupDisplayName() {
        return "EasyDoc";
    }

    @NotNull
    public String getDisplayName() {
        return "Checkstyle";
    }

    @NotNull
    public String getShortName() {
        return "checkstyle";
    }

    public void readSettings(Element node) throws InvalidDataException {
        globalConfig.readExternal(node);
        if (optionsPanel != null) {
            optionsPanel.reset(globalConfig);
        }
    }

    public void writeSettings(Element node) throws WriteExternalException {
        if (optionsPanel != null) {
            optionsPanel.save(globalConfig);
        }
        globalConfig.writeExternal(node);
    }
}
